﻿using KinonekoSoftware.Extensions.Strings.Core;

namespace KinonekoSoftware.Extensions.Strings.SearchModels.TrieTree
{
    public class TrieTreeSearchPattern<T> : TrieTreeSearchPatternBase,ISearchPattern<T>
    {
        private static readonly MatchResult<T>[] Empty = Array.Empty<MatchResult<T>>();

        private int               _version;
        private IGlossaryProvider<T> _glossary;

        MatchResult<T> GetMatchedResult(int offset, int length, Span<char> span)
        {
            var key  = new string(span.Slice(offset, length));
            return new MatchResult<T>(offset, length, true, _glossary.GetValue(key));
        }

        static MatchResult<T> GetUnmatchedResult(int offset, int length)
        {
            return new MatchResult<T>(offset, length, false, default(T));
        }

        public void Build(IGlossaryProvider<T> glossary)
        {
            var root = ((ITrieTreeGlossaryProviderRoot)glossary).Root;
            
            //
            // 
            BuildFail(root);

            //
            //
            _glossary = glossary;

            //
            //
            _version = glossary.Version;
        }


        private bool StartMatch(ref int i, char[] input, Node root, Node node, out MatchResult <T>result)
        {
            var offset  = i++;
            var length  = 1;
            var str_len = input.Length;

            for (; i < str_len;i++)
            {
                //
                //
                var ch   = input[i];
                var next = node.Get(ch);

                //
                // shi pei
                if (next is null)
                {
                    while (node is null && !ReferenceEquals(node, root))
                    {
                        node = node.Fail;
                        next = node.Get(ch);
                    }

                    if (next is not null)
                    {
                        //
                        //
                        length++;
                        //
                        //
                        node = next;
                    }
                    else
                    {
                        result = GetMatchedResult(offset, length, input);

                        return true;
                    }

                }
                else
                {
                    node = next;
                    length++;
                }
                
                if (next.IsWordEnd)
                {
                    //
                    //
                    ch = input[i + 1];
                    var temp = node.Get(ch);

                    if (temp is not null)
                    {
                        node = temp;
                        length++;
                        i ++;
                    }
                    else
                    {
                        result = GetMatchedResult(offset, length, input);

                        return true;
                    }

                }

            }

            result = GetMatchedResult(offset, length, input);

            return true;
        }

        public IList<MatchResult<T>> Match(string input)
        {
            if (string.IsNullOrEmpty(input))
            {
                return Empty;
            }

            if (_glossary is null)
            {
                return Empty;
            }

            var root    = ((ITrieTreeGlossaryProviderRoot)_glossary).Root;
            var offset  = 0;
            var results = new List<MatchResult<T>>(16);
            var str_len = input.Length;
            var array   = input.ToCharArray();

            for (var i = 0; i < str_len;i++)
            {
                var ch   = array[i];
                var node = root.Get(ch);


                if (node is not null)
                {
                    var old = i;
                    
                    if (StartMatch(ref i, array, root, node, out var newResult))
                    {
                        if (offset == 0 && newResult.Offset == offset)
                        {
                            results.Add(newResult);
                        }
                        else
                        {
                            
                            results.Add(GetUnmatchedResult(offset, newResult.Offset - offset ));
                            results.Add(newResult);

                        }
                        offset = old + newResult.Length;
                    }
                    else
                    {
                        i++;
                    }
                }

            }

            if (offset < str_len)
            {
                results.Add(GetUnmatchedResult(offset, str_len - offset));
            }

            return results;
        }

        public int Version => _version;
    }
}